﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using AiFrame.InterfaceLib;
using AiFrame.InterfaceLib.Configuration;
using AiFrame.InterfaceLib.MVP;
using AiFrame.InterfaceLib.Plugins;
using AiFrame.InterfaceLib.Plugins.Events;
using AiFrame.InterfaceLib.Policy.ProductLine;
using AiFrame.InterfaceLib.Windows.Themes;

namespace AiFrame.Plugins.PluginManager
{
    /// <summary>
    /// Diese Klasse stellt den PluginManager dar. Für weitere
    /// Beschreibungen bitte im Interface IPluginManager nachsehen.
    /// </summary>
    public class PluginManager: IPluginManager
    {
        private List<IPlugin> _pluginCollection;
        private ICoreInterfaceFactory _coreInterfaceFactory = null;
        private IProductLine _productLine;
        private ITheme _applicationTheme;
        private string _applicationDirectory;

        /// <summary>
        /// Lädt ein einzelnes Plugin. Dies ist eine generische Methode,
        /// d.h. es können neben IPlugin-Objekten auch IProductLine-Objekte
        /// usw. geladen werden.
        /// </summary>
        /// <param name="dllName"></param>
        /// <returns></returns>
        private T LoadPlugin<T>(string dllName)
        {
            if (dllName == null) throw new ArgumentNullException("dllName");

            Assembly assembly = null;
            Type[] types = null;
            
            try
            {
                assembly = Assembly.LoadFrom(dllName);
                types = assembly.GetExportedTypes();
            }
            catch(Exception e)
            {
                throw new PluginLoadException(string.Format("Cannot load Plugin: {0}", e.Message));
            }
                        
            /* Durchsuchen der DLL. Der erste gefundene Typ, der sich nach IPlugin
             * umwandeln lässt, wird als Treiber angenommen */
            foreach (Type t in types)
            {
                if (typeof(T).IsAssignableFrom(t))
                {
                    ConstructorInfo ci = t.GetConstructor(new Type[0]);
                    T plugin = (T)ci.Invoke(new Object[0]);
                    return plugin;
                }
            }
            throw new PluginLoadException("Es wurde keine öffentliche IPlugin-Klasse in der Assembly gefunden: " + Path.GetFileName(dllName));
        }

        public PluginManager()
        {
            _pluginCollection = new List<IPlugin>();
        }

        #region Implementation of IPluginManager

        public void LoadSystemLibraries()
        {
            /* Produktlinien-Informationen laden */
            _productLine = LoadPlugin<IProductLine>("Policy.ProductLine.dll");
            
            /* Das Application-Theme laden */
            _applicationTheme = LoadPlugin<ITheme>("Windows.Theme.dll");
        }

        private List<string> ReadFiles(string applicationDirectory)
        {
            List<string> pluginCollec = new List<string>();
            
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(applicationDirectory + "\\Plugins.PluginManager.xml");
            XmlElement root = xmlDocument.DocumentElement;
            foreach (XmlNode plugin in root.ChildNodes)
            {
                Console.Out.WriteLine(plugin.InnerText);
                pluginCollec.Add(plugin.InnerText);
            }
            
            return pluginCollec;
        }

        public void LoadPlugins(string applicationDirectory)
        {
            _applicationDirectory = applicationDirectory;

            /* Prüfen, ob bereits initialisiert wurde. Wenn nicht,
             * soll eine Exception geworfen werden */
            if (_coreInterfaceFactory == null)
                throw new MissingInitialisationException("Vor dem Laden der Plugins" 
                                                         + " muss eine Initialisierung stattfinden");

            /* Alt: Alle DLL-Dateien im PluginManager-Ordner suchen */
            List<string> files = ReadFiles(applicationDirectory);

            foreach (string s in files)
            {
                try
                {
                    IPlugin plugin = LoadPlugin<IPlugin>(applicationDirectory + "\\" + s);
                    /* PluginManager laden und ein CoreInterface-Objekt mitgeben */
                    if (plugin != null)
                    {
                        plugin.Load(_coreInterfaceFactory.CreateCoreInterface(), s);
                        _pluginCollection.Add(plugin);
                    }
                }
                catch (PluginLoadException)
                {
                    // Diese Exception ignorieren
                }
                catch (Exception e)
                {
                    throw e;
                }
            }
        }

        public void UnloadPlugins()
        {
            throw new System.NotImplementedException();
        }

        public void Init(ICoreInterfaceFactory coreInterfaceFactory)
        {
            _coreInterfaceFactory = coreInterfaceFactory;
        }

        public IProductLine GetProductLine()
        {
            return _productLine;
        }

        public ITheme GetApplicationTheme()
        {
            return _applicationTheme;
        }

        public IStartupConfiguration LoadConfigurationModule(string filename)
        {
            IStartupConfiguration configuration = LoadPlugin<IStartupConfiguration>(filename);
            configuration.Load(_coreInterfaceFactory.CreateCoreInterface(),
                _applicationDirectory);

            return configuration;
        }

        public event EventHandler<WindowChangedEventArgs> WindowsLoadedReady;
        public event EventHandler<WindowChangedEventArgs> NewDatasetAssigned;
        public void InvokeNewDatasetAssignedEvent<T>(object wnd)
        {
            if (wnd is ISubWindowContainer<T>)
                (wnd as ISubWindowContainer<T>).CloseSubWindows();
            
            if (NewDatasetAssigned != null)
                NewDatasetAssigned(this, new WindowChangedEventArgs(wnd));
        }

        #endregion
       
        public void OnWindowRegisteredEvent(object wnd)
        {
            _pluginCollection.Sort(new DescendingWREComparer());
            foreach (IPlugin plugin in _pluginCollection)
            {
              if (plugin is IWindowRegisteredEvent)
                  ((IWindowRegisteredEvent)plugin).OnWindowRegisteredEvent(wnd);
            }

            if (WindowsLoadedReady != null)
                WindowsLoadedReady(this, new WindowChangedEventArgs(wnd));
        }

        #region Implementation of IConnectionReadyEvent

        public void OnConnectionReadyEvent()
        {
            foreach (IPlugin plugin in _pluginCollection)
            {
                if (plugin is IConnectionReadyEvent)
                    ((IConnectionReadyEvent)plugin).OnConnectionReadyEvent();
            }
        }

        #endregion
    }

    /// <summary>
    /// Stellt einen IComparer für das WindowRegisteredEvent dar.
    /// 
    /// Dabei wird mit diesem Comparer in der benutzten Liste das
    /// PriorWindowRegisteredEvent zuerst ausgegeben, da es eine höhere
    /// Priorität erhält.
    /// </summary>
    public class DescendingWREComparer: IComparer<IPlugin>
    {
        #region Implementation of IComparer

        public int Compare(IPlugin x, IPlugin y)
        {
            if ((x is IPriorWindowRegisteredEvent) && !(y is IPriorWindowRegisteredEvent))
                return -1;
            if ((y is IPriorWindowRegisteredEvent) && !(x is IPriorWindowRegisteredEvent))
                return 1;
            return 0;
        }

        #endregion
    }
}